2019前端体系及核心知识点总结
深冻结
1 | let person = { |
防抖
1 | debounce(fn, delay) { |
节流
1 | // 定时器实现 |
可存储函数执行结果的函数
1 | memeorize(fn) { |
将函数转化为 Promise 的使用
1 | promisy(fn) { |
函数柯里化
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
参数复用、提前返回和延迟执行
1
2
3
4
5
6
7 // Tips:伪数组转真数组
// Array.prototype.slice.call({
// length: 2,
// '0': '123',
// '1': 'aaa'
// });
// let argsArr = [].slice.call(arguments);
1 | currying(fn) { |
1 | // 认识真相的五大阻力: |
1 | // https://juejin.im/post/5cef46226fb9a07eaf2b7516 |
1 | 前端2018 |
JS
1 | // Primitive/Object |
ES6
1 | * hoisting: 提升的是声明 |
- 并发(concurrency)和并行(parallelism)
// 并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
// 并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。 - Callback (Callback hell)
// 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
// 嵌套函数一多,就很难处理错误
1.Generator
2.Promise// 控制函数的执行 // 返回一个迭代器 // 可以通过 Generator 函数解决回调地狱的问题
3.async 及 await// 很好地解决了回调地狱的问题 // 构造 Promise 的时候,构造函数内部的代码是立即执行的 // Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装 // 无法取消 Promise,错误需要通过回调函数捕获
// 一个函数如果加上 async ,那么该函数就会返回一个 Promise // async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用 // await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator,会让出线程,阻塞后面的代码先执行async外的同步代码 // 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果 // 优雅地解决回调地狱问题 // 缺点:await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低
- setTimeout、setInterval、requestAnimationFrame
// JS 是单线程执行的,如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行
// requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题
// 定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码 Event Loop
// https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
// 进程与线程:进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。线程是进程中的更小单位,描述了执行一段指令所需的时间
// 执行栈:存储函数调用的栈结构,遵循先进后出的原则,执行 JS 代码的时候其实就是往执行栈中放入函数,遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为
// 在程序的整个生命周期中不停的重复 主任务 ——> micro task ——> 渲染视图 ——> macro task 的操作
// 微任务会在当前宏任务的同步代码执行完毕,才会依次执行
1.浏览器中// 首先执行同步代码,这属于宏任务 // 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行 // 执行所有微任务 // 当执行完所有微任务后,如有必要会渲染页面 // 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数 // 不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task // 宏任务包括:Events,Parsing,Callbacks,Using a resource,Reacting to DOM manipulation,script,setTimeout,setInterval,setImmediate,I/O,UI rendering // 微任务包括:process.nextTick,promise,MutationObserver,其中 process.nextTick 为 Node 独有
2.Node中
// 分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段 2.1 macrotask timer // 执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的 I/O // 处理一些上一轮循环中的少数未执行的 I/O 回调 idle,prepare poll // 回到 timer 阶段执行回调;执行 I/O 回调 check // 执行 setImmediate close callbacks // 执行 close 事件 2.2 microtask Timers IO Callbacks IO Polling Set Immediate Close Events
- process.nextTick
// 它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行 - 事件触发有三个阶段
1.window 往事件触发处传播,遇到注册的捕获事件会触发
2.传播到事件触发处时触发注册的事件
3.从事件触发处往 window 传播,遇到注册的冒泡事件会触发
// 使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false ,useCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性:
// stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件1.capture:布尔值,和 useCapture 作用一样 2.once:布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听 3.passive:布尔值,表示永远不会调用 preventDefault
// stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件
// 事件代理的方式相较于直接给目标注册事件:节省内存、不需要给子节点注销事件 跨域同源策略:主要是用来防止 CSRF 攻击(利用用户的登录态发起恶意请求)
// 请求发出去了,被浏览器拦截了响应
1.JSONPfunction jsonp(url, jsonpCallback, success) { let script = document.createElement('script') script.src = url script.async = true script.type = 'text/javascript' window[jsonpCallback] = function(data) { success && success(data) } document.body.appendChild(script) } jsonp('http://xxx', 'callback', function(value) { console.log(value) })
2.CORS
// 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS // 简单请求 // 复杂请求:预检请求 OPTIONS
3.document.domain
// 用于二级域名相同的情况
4.postMessage
// 用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息 // 发送消息端 window.parent.postMessage('message', 'http://test.com') // 接收消息端 var mc = new MessageChannel() mc.addEventListener('message', event => { var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') { console.log('验证通过') } })
5.nginx反向代理
- cookie
// http-only 不能通过 JS 访问 Cookie,减少 XSS 攻击
// secure 只能在协议为 HTTPS 的请求中携带
// same-site 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 - Service Worker
// 请求拦截和结果缓存
// 独立线程,网络代理,一般可以用来实现缓存功能
// 传输协议必须为 HTTPS,涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全
1.注册 Service Worker
2.监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据 - Web Worker
// 开辟线程执行耗时js - 浏览器缓存机制
缓存位置
缓存策略1.Service Worker 2.Memory Cache // 对于大文件来说,大概率是不存储在内存中的,反之优先 // 当前系统内存使用率高的话,文件优先存储进硬盘 3.Disk Cache // 容量和存储时效性 // 即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据 4.Push Cache // HTTP/2 // 缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放 // 可以推送 no-cache 和 no-store 的资源 // 一旦连接被关闭,Push Cache 就被释放 // 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存 // Push Cache 中的缓存只能被使用一次 // 浏览器可以拒绝接受已经存在的资源推送 // 你可以给其他域名推送资源 5.网络请求
// 强缓存和协商缓存 // 缓存策略都是通过设置 HTTP Header 来实现 1.强缓存 // Expires 和 Cache-Control // 表示在缓存期间不需要请求,state code 为 200 // Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效 // Cache-Control 优先级高于 Expires, max-age=30 该属性值表示资源会在 30 秒后过期,需要再次请求 2.协商缓存 // Last-Modified 和 ETag Last-Modified 和 If-Modified-Since // Last-Modified 表示本地文件最后修改日期 // If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码 弊端: // 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 // 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源 ETag 和 If-None-Match // ETag 优先级比 Last-Modified 高 // 未设置缓存策略:启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间
浏览器渲染原理
1.解析 HTML:Byte Data -> String -> Token -> Node -> DOM Tree
2.解析 CSS:Byte Data -> String -> Token -> Node -> CSSOM
3.Render Tree:DOM Tree + CSSOM
4.当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上Tip:插入几万个 DOM,如何实现页面不卡顿?
// requestAnimationFrame
// 虚拟滚动(virtualized scroller):只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容// defer:立即下载,但延迟执行
// async:JS 文件下载和解析不会阻塞渲染// 关键渲染路径
- 安全
1.XSS 分持久型和非持久型
2.CSRF// 转义字符 // 白名单 // CSP // 设置 HTTP Header 中的 Content-Security-Policy // 设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">
3.点击劫持// SameSite // 验证 Referer // Token
4.中间人攻击// X-FRAME-OPTIONS 一个 HTTP 响应头,防御用 iframe 嵌套的点击劫持攻击 // DENY,表示页面不允许通过 iframe 的方式展示 // SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示 // ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示
// 中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了
- 性能优化
// 图片优化
//
//
//
// 懒执行
// 懒加载
// CDN - Webpack 性能优化
- 减少 Webpack 打包时间
1.1 优化 Loader
// 优化 Loader 的文件搜索范围:include、exclude
// 将 Babel 编译过的文件缓存起来:loader: ‘babel-loader?cacheDirectory=true’
1.2 HappyPack
// HappyPack 可以将 Loader 的同步执行转换为并行的
module: {
loaders: [
]{ test: /\.js$/, include: [resolve('src')], exclude: /node_modules/, // id 后面的内容对应下面 loader: 'happypack/loader?id=happybabel' }
},
plugins: [
new HappyPack({
})id: 'happybabel', loaders: ['babel-loader?cacheDirectory'], // 开启 4 个线程 threads: 4
]
1.3 DllPlugin
// DllPlugin 可以将特定的类库提前打包然后引入
// webpack.DllPlugin()
// webpack.DllReferencePlugin()
1.4 代码压缩
// Webpack3: webpack-parallel-uglify-plugin 来并行运行 UglifyJS
// Webpack4: mode设置production - 减少 Webpack 打包后的文件体积
2.1 按需加载
// 每个路由页面单独打包为一个文件
2.2 Scope Hoisting
// 分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去
// Webpack4: optimization.concatenateModules = true
2.3 Tree Shaking
// 实现删除项目中未被引用的代码
2.4 开启gzip压缩
- 减少 Webpack 打包时间
- 自己实现一个Webpack打包工具?
// https://www.youtube.com/watch?v=Gc9-7PBqOC8&list=LLHK1mTHpwrUeYgF5gu-Kd4g
// https://github.com/ronami/minipack/blob/master/src/minipack.js - MVC or MVVM
// MVC 有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况
// 通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象 - Virtual DOM
// 对比完两棵树以后,就可以通过差异去局部更新 DOM,实现性能的最优化
1.将 Virtual DOM 作为一个兼容层,让我们还能对接非 Web 端的系统,实现跨端开发
2.通过 Virtual DOM 我们可以渲染到其他的平台,比如实现 SSR、同构渲染等
3.实现组件的高度抽象化 前端路由
// 监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面
1.Hash 模式// 更简单,并且兼容性也更好 window.addEventListener('hashchange', () => { })
2.History 模式
// history.pushState 和 history.replaceState // 新增历史记录 history.pushState(stateObject, title, URL) // 替换当前历史记录 history.replaceState(stateObject, title, URL) // 点击后退按钮时会触发 popState 事件 window.addEventListener('popstate', e => { // e.state 就是 pushState(stateObject) 中的 stateObject console.log(e.state) })
Vue
- 组件通信
1.父子通信
2.兄弟组件通信// 单向数据流 // props 传递数据给子组件,子组件不能直接修改 props,必须通过发送事件的方式告知父组件修改数据 // $attrs 属性透传 // emit 发送事件传递数据给父组件 // v-mode 语法糖 默认会解析成名为 value 的 prop 和名为 input 的事件 // 通过访问 $parent 或者 $children 对象来访问组件实例中的方法和数据 // $listeners 属性会将父组件中的 (不含 .native 修饰器的) v-on 事件监听器传递给子组件,子组件可以通过访问 $listeners 来自定义监听器 // .sync 属性是个语法糖 <!--父组件中--> <input :value.sync="value" /> <!--以上写法等同于--> <input :value="value" @update:value="v => value = v"></comp> <!--子组件中--> <script> this.$emit('update:value', 1) </script>
3.跨多层次组件通信// 通过查找父组件中的子组件实现 this.$parent.$children
4.任意组件// provide / inject:Vue 2.2 新增的 API
// Vuex // Event Bus
- Vue.extend()
// 扩展组件生成一个构造器,通常会与 $mount 一起使用 - mixin & mixins
// mixin 用于全局混入,会影响到每个组件实例
// mixins 扩展组件的方式,上拉下拉加载数据这种逻辑(先于组件内的钩子函数执行) - computed & watch
// computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容
// watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作
// computed 和 watch 还都支持对象的写法 - keep-alive
// 保存一些组件的状态防止多次渲染
// 拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated
// 缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数 - $attrs
// 属性透传,使用起来也比较简单,避免了多写 props - 响应式原理
1.Object.defineProperty() getter/setter 劫持了数据的读写操作,使我们可以在数据读写时得到通知并执行自定义的操作
2.递归遍历
3.订阅/发布: Dep 和 Watcher
4.数组的特殊响应化处理 Object.create(Array.prototype)
5.精准依赖收集:为每个键都维护一个 Dep
6.异步更新队列:避免不必要的计算和 DOM 操作(MutationObserver 与 Promise.then 中的回调均在下一个 microTask 中执行)
7.key 的添加与删除(每个 key 对应值为对象/数组创建了另一个 dep,挂在该对象和数组的 ob 属性上,setter 操作,通知 set/get 中的 dep;非 setter 操作,如对象 key 添加删除、数组变异方法调用,通知 ob 中的 dep)
8.深度数据追踪:递归进去遍历每一个子属性,主动触发一下 getter
9.Proxy:支持监测数组的 push 等方法操作,支持对象属性的动态添加和删除,极大的简化了响应化的代码量
// 依赖收集:在 getter 过程中标记当前的键,仅在被标记的键修改时,才去触发订阅的更新(在键的 getter 触发时将当前 watcher 加入 dep 中,完成依赖收集)
// 简单实现:data-reactivity-system-demo.js
// 通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的 - 编译过程
1.将模板解析为 AST
2.优化 AST// 基本的 AST 对象 { // 类型 type: 1, // 标签 tag, // 属性列表 attrsList: attrs, // 属性映射 attrsMap: makeAttrsMap(attrs), // 父节点 parent, // 子节点 children: [] } // 根据这个最基本的 AST 对象中的属性,进一步扩展 AST
3.将 AST 转换为 render 函数// 静态内容提取 // 提取静态的属性
// 历整个 AST,根据不同的条件生成不同的代码
nextTick 原理分析
// 本质是 microtask!!
// MutationObserver 和 Promise.then(Vue 2.0.0-rc.7曾尝试用window.postMessage,是macrotask,不可行)
// Vue.nextTick和Vue.prototype.$nextTick都是直接使用了这个nextTick
// 在batcher中,也就是watcher观测到数据变化后执行的是nextTick(flushBatcherQueue),flushBatcherQueue则负责执行完成所有的dom更新操作
// nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM
// 在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on
// 实现 macrotasks:会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel ,以上都不行的话就使用 setTimeout
// 最新版的Vue源码里,优先使用Promise.resolve().then(nextTickHandler)来将异步回调放入到microtask中(MO在IOS9.3以上的WebView中有bug),没有原生Promise才用MO
if (typeof setImmediate !== ‘undefined’ && isNative(setImmediate)) {macroTimerFunc = () => { setImmediate(flushCallbacks) }
} else if (
typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) }
} else {
macroTimerFunc = () => { setTimeout(flushCallbacks, 0) }
React
// Fiber 机制(V16)
// 对于异步渲染,现在渲染有两个阶段:reconciliation 和 commit 。前者过程是可以打断的,后者不能暂停,会一直更新界面直到完成。
Reconciliation 阶段1.componentWillMount 2.componentWillReceiveProps -> getDerivedStateFromProps 3.shouldComponentUpdate 4.componentWillUpdate -> getSnapshotBeforeUpdate
Commit 阶段
1.componentDidMount 2.componentDidUpdate 3.componentWillUnmount
setState
// 是个异步 API,多次调用放入一个队列中,在恰当的时候统一进行更新过程
// 如果想实时:
this.setState((prevState) => ({ count: prevState.count + 1 }), () => {console.log(this.state)
})
性能优化
// shouldComponentUpdate 函数中我们可以通过返回布尔值来决定当前组件是否需要更新。这层代码逻辑可以是简单地浅比较一下当前 state 和之前的 state 是否相同,也可以是判断某个值更新了才触发组件更新
// React.PureComponent
// React.memo()通信
1.父子通信// 父组件通过 props 传递数据给子组件,子组件通过调用父组件传来的函数传递数据给父组件 // 单向数据流
2.兄弟组件通信
// 通过共同的父组件来管理状态和事件函数
3.跨多层级组件通信
// React.createContext()
4.任意组件
// Redux // Event Bus
高阶组件(HOC)
事件机制
// 合成事件(SyntheticEvent)Hooks
// 抽离重复逻辑 不会增加组件的嵌套 实现状态的共享
// 通过函数组件的方式去管理状态,并且也能将四散的业务逻辑写成一个个 Hooks 便于复用以及维护
useState
useEffect
useRef
useCallback- 监控
页面埋点
性能监控
异常监控// performance.getEntriesByType('navigation') // https://www.jianshu.com/p/47a6b7866ba6
- UDP
1.面向无连接
2.不可靠性// 数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作 // 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了 // 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
3.高效// 某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP
4.传输方式// UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
// UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能
- HTTP/2
1.二进制传输
2.多路复用 帧(frame)和流(stream)
3.Header 压缩
4.服务端 Push
5. - HTTP/3
QUIC
// HTTP/2 通过多路复用、二进制流、Header 压缩等等技术,极大地提高了性能,但是还是存在着问题的多路复用 0-RTT 纠错机制
// QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议,该协议基于 UDP,又取了 TCP 中的精华,实现了即快又可靠的协议
1 | * 设计模式 |